import "reflect-metadata";
import {DECORATOR_SUFFIX} from "@framework/decorator";
import DefaultListableBeanFactory from "@framework/bean/DefaultListableBeanFactory";
import KoaBootRouter from "@framework/web/KoaBootRouter";
import StrUtils from "@framework/util/StrUtils";
import GlobalAutoConfigurable from "@framework/config/GlobalAutoConfigurable";
import {Context} from "koa";
import KoaBootLogger from "@framework/web/KoaBootLogger";


/**
 * 负责解析Koa目录，并且路由实例注册至 KoaBoot 服务中
 */
class KoaBootRouterProvider {

    /**
     * 系统日志
     * @private
     */
    private logger: KoaBootLogger = KoaBootLogger.getInstance();

    /**
     * Bean构造工厂
     * @private
     */
    private beanFactory: DefaultListableBeanFactory;

    /**
     * 构造函数
     * @param beanFactory
     */
    constructor(beanFactory: DefaultListableBeanFactory) {
        this.beanFactory = beanFactory;
        this.constructKoaBootRouter();
    }

    /**
     * 构造KoaBoot路由
     */
    public constructKoaBootRouter() {
        this.logger.debug("Loader KoaRouter Object")
        const pathPrefix = GlobalAutoConfigurable.koaBoot.prefix;
        this.logger.debug(`KoaBoot Router Prefix: ${pathPrefix}`);
        for (const beanDefinition of this.beanFactory.getBeanDefinitions().values()) {

            // 反射获取 KoaBootRouter 对象
            const target = beanDefinition.getTarget();
            const koaBootRouter:KoaBootRouter = Reflect.getOwnMetadata(DECORATOR_SUFFIX.generator(target.name, DECORATOR_SUFFIX.WEB), target);
            if (!koaBootRouter) {
                this.logger.debug(`${target.name} Not Found Router`)
                continue;
            }
            const prefix = koaBootRouter.getPrefix();
            const router = koaBootRouter.getRouter();
            const routers = koaBootRouter.getRouters();
            const instance: any = beanDefinition.getInstance();

            // 设置统一前缀，读取配置文件
            router.prefix(pathPrefix);

            // 匹配 RESETFUL风格的参数,暂不支持
            // const regex = /\{[^{}]+\}/g;

            // 解析KoaBootRouter对象，封装koa路由对象
            for (const route of routers) {
                const url = (StrUtils.isBlank(prefix) ? route.getUrl() : `${prefix}/${route.getUrl()}`).replace("//", "/");
                this.logger.debug(`${route.getMethod().toLocaleUpperCase()} Path: ${url} By Class Method ${route.getFunName()}`);
                (router as any)[route.getMethod().toLocaleLowerCase()](url, async (ctx: Context) => await this.handlerRouter(ctx, instance, route.getFunName(), target, route.getReturnType()));
            }
        }
        this.logger.info("Auto Koa Router Ends")
    }

    /**
     * 负责调用对象的方法
     * @param ctx koa-router 全局上下文
     * @param instance 类对象实例
     * @param method 类对象实例方法名称
     * @private
     */
    private async handlerRouter(ctx: Context, instance: any, method: string, target: any, returnType: boolean) {
        this.logger.debug(`Initialize Router ${ctx.request.href}`);

        if (!(instance instanceof Object && instance[method] instanceof Function)) {
            throw new Error(`无法在容器中找到[${instance}]或者此类[${instance.constructor.name}]中没有可执行的方法`)
        }

        // 构造方法参数
        const parameter = this.genParameter(ctx, method, target);

        // 执行方法
        try {

            // 执行映射的类方法
            const response = await instance[method](...parameter);

            // 日志打印
            this.logger.info(`Invoke Class Method ${method}`);

            // 返回值存在则进行响应
            returnType && response && (ctx.body = response);
        } catch (e) {
            this.logger.error(e);
            ctx.response.status = 500;
            ctx.body = {
                code: 500,
                data: null,
                message: (e as Error).message
            };
        }
    }

    /** 构建需要执行的参数，注入至映射的方法中
     * @param ctx
     * @param target
     */
    private genParameter(ctx: Context, method: string, target: any): Array<any> {
        const parameter: any[] = [];
        const contextMetadataKey = DECORATOR_SUFFIX.generator(target.name, DECORATOR_SUFFIX.CONTEXT) + method;
        const queryParamsMetadataKey = DECORATOR_SUFFIX.generator(target.name, DECORATOR_SUFFIX.QUERY) + method;
        const bodyParamsMetadataKey = DECORATOR_SUFFIX.generator(target.name, DECORATOR_SUFFIX.BODY) + method;

        // 解析参数，如果存在ctx则进行参数注入
        const parameterIndex = Reflect.getMetadata(contextMetadataKey, target);
        if (typeof parameterIndex == "number") {
            parameter.splice(parameterIndex, 0, ctx);
        }

        // 普通参数注入
        const queryParameterIndexArray: Array<Map<string, number>> = Reflect.getMetadata(queryParamsMetadataKey, target);
        if (queryParameterIndexArray && queryParameterIndexArray.length > 0) {
            const query = ctx.request.query;
            for (let map of queryParameterIndexArray) {
                for (let [parameterName, parameterIndex] of map) {
                    const queryElement = query[parameterName];
                    parameter.splice(parameterIndex, 0,  queryElement);
                }
            }
        }

        // body参数注入
        const bodyParameterIndexArray: Array<number> = Reflect.getMetadata(bodyParamsMetadataKey, target);
        if (bodyParameterIndexArray && bodyParameterIndexArray.length > 0) {
            const body = ctx.request.body;
            for (let parameterIndex of bodyParameterIndexArray) {
                parameter.splice(parameterIndex, 0,  body);
            }
        }

        return parameter;
    }
}



export default KoaBootRouterProvider;